/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.zkex.simple;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.GetListRequestMessage;
import cn.easyplatform.messages.request.MapRequestMessage;
import cn.easyplatform.messages.vos.GetListVo;
import cn.easyplatform.messages.vos.component.ListMapVo;
import cn.easyplatform.messages.vos.component.ListPageVo;
import cn.easyplatform.messages.vos.component.MapVo;
import cn.easyplatform.messages.vos.datalist.ListGetListVo;
import cn.easyplatform.spi.service.ComponentService;
import cn.easyplatform.type.FieldType;
import cn.easyplatform.type.FieldVo;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.type.ListRowVo;
import cn.easyplatform.web.dialog.MessageBox;
import cn.easyplatform.web.ext.Assignable;
import cn.easyplatform.web.ext.ComponentBuilder;
import cn.easyplatform.web.ext.Node;
import cn.easyplatform.web.ext.PairItem;
import cn.easyplatform.web.ext.zul.BandboxExt;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.zkex.ListSupport;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.support.ExpressionEngine;
import cn.easyplatform.web.task.support.SupportFactory;
import cn.easyplatform.web.task.zkex.list.PanelSupport;
import cn.easyplatform.web.utils.PageUtils;
import cn.easyplatform.web.utils.WebUtils;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.*;
import org.zkoss.zul.event.PagingEvent;
import org.zkoss.zul.event.ZulEvents;

import java.util.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class BandboxBuilder implements ComponentBuilder, EventListener<Event>, Assignable {

    //默认列表
    private final static int LIST = 0;
    //具有上下层结构
    private final static int LEVEL = 1;
    //延时加载
    private final static int GROUP = 2;
    //双查询
    private final static int DUAL = 3;
    //分组
    private final static int DEFAULT = 4;

    private OperableHandler main;

    private BandboxExt bg;

    private ListSupport support;

    private Component anchor;

    private int type = DEFAULT;

    public BandboxBuilder(OperableHandler mainTaskHandler, BandboxExt bg) {
        this.main = mainTaskHandler;
        this.bg = bg;
    }

    public BandboxBuilder(ListSupport support, BandboxExt bg,
                          Component anchor) {
        this.support = support;
        this.main = support.getMainHandler();
        this.bg = bg;
        this.anchor = anchor;
    }

    @Override
    public Component build() {
        if (Strings.isBlank(bg.getQuery()))
            throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<bandbox>", "query");
        if (Strings.isBlank(bg.getValueField()))
            throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<bandbox>", "valueField");
        if (Strings.isBlank(bg.getLabelField()))
            throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<bandbox>", "labelField");
        if (BandboxExt.DEFAULT.equalsIgnoreCase(bg.getType())) {
            if (Strings.isBlank(bg.getGroupField()))
                type = LIST;
            else
                type = DEFAULT;
        } else {
            if (Strings.isBlank(bg.getGroupField()))
                throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<bandbox>", "groupField");
            if (BandboxExt.DUAL.equalsIgnoreCase(bg.getType())) {
                if (Strings.isBlank(bg.getGroupQuery()))
                    throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<bandbox>", "groupQuery");
                type = DUAL;
            } else if (BandboxExt.GROUP.equalsIgnoreCase(bg.getType())) {
                if (Strings.isBlank(bg.getGroupQuery()))
                    type = LEVEL;
                else
                    type = GROUP;
            }
        }
        if (bg.getShowType() == 0)
            bg.setShowType(BandboxExt.POPUP);
        if (bg.getShowType() == BandboxExt.DROPDOWN)
            bg.appendChild(new Bandpopup());
        bg.setAttribute("$proxy", this);
        PageUtils.checkAccess(main.getAccess(), bg);
        bg.addEventListener(Events.ON_OPEN, this);
        PageUtils.processEventHandler(main, bg, anchor);
        return bg;
    }

    @Override
    public void onEvent(Event event) throws Exception {
        if (event.getName().equals(Events.ON_OPEN)) {
            OpenEvent oe = (OpenEvent) event;
            if (oe.isOpen()) {
                if (oe.getTarget().getFirstChild() == null || oe.getTarget().getFirstChild().getFirstChild() == null) {
                    GetListVo gv = null;
                    if (anchor != null) {
                        ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                        //ListRowVo selectedValue = support.getSelectedValue();
                        //if (selectedValue != null)
                        //    rowVo = selectedValue;
                        Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                                .getKeys();
                        gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), support
                                .getComponent().getId(), data);
                    } else if (main instanceof PanelSupport) {
                        ListSupport ls = ((PanelSupport) main).getList();
                        gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), ls
                                .getComponent().getId(), null);
                    } else
                        gv = new GetListVo(bg.getDbId(), bg.getQuery());
                    gv.setFilter(bg.getFilter());
                    gv.setPageSize(bg.isFetchAll() ? 0 : bg.getPageSize());
                    gv.setOrderBy(bg.getOrderBy());
                    gv.setGetCount(gv.getPageSize() > 0);
                    GetListRequestMessage req = new GetListRequestMessage(main.getId(),
                            gv);
                    ComponentService cs = ServiceLocator
                            .lookup(ComponentService.class);
                    IResponseMessage<?> resp = cs.getList(req);
                    if (!resp.isSuccess())
                        MessageBox.showMessage(resp);
                    else {
                        if (type == LIST)
                            new ListBuilder().build(resp.getBody());
                        else
                            new GroupBuilder().build(resp.getBody());
                    }
                }
            }//if
        }//if
    }

    @Override
    public void setValue(Object value) {
        if (value != null && !value.toString().trim().equals("")) {
            String query = bg.getValueQuery();
            String[] params = value.toString().split(bg.getSeparator() == null ? "," : bg.getSeparator());
            StringBuilder sb = new StringBuilder();
            if (Strings.isBlank(query)) {
                query = type == DUAL ? bg.getGroupQuery() : bg.getQuery();
                String str = query.toLowerCase();
                int start = str.indexOf("from") + 4;
                int end = str.indexOf("where");
                String table = end > 0 ? query.substring(start, end) : query.substring(start);
                sb.append("SELECT ").append(bg.getValueField()).append(",").append(bg.getLabelField()).append(" FROM ").append(table).append(" WHERE ").append(bg.getValueField()).append(" IN ");
                query = sb.toString();
                sb.setLength(0);
                sb.append(query).append("(");
                for (int i = 0; i < params.length; i++)
                    sb.append("?,");
                sb.deleteCharAt(sb.length() - 1);
                sb.append(")");
                query = sb.toString();
            } else {
                sb.append(bg.getValueField()).append(" IN (");
                for (int i = 0; i < params.length; i++)
                    sb.append("?,");
                sb.deleteCharAt(sb.length() - 1);
                sb.append(")");
                query = query.replace("$" + bg.getValueField(), sb.toString());
            }
            GetListVo gv = new GetListVo(bg.getDbId(), query);
            for (String val : params) {
                if (!Strings.isBlank(bg.getValueType()))
                    gv.setParameter(FieldType.cast(val, FieldType.getType(bg.getValueType())));
                else
                    gv.setParameter(val);
            }
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            IResponseMessage<?> resp = cs.getList(new GetListRequestMessage(main.getId(), gv));
            if (!resp.isSuccess())
                throw new BackendException(resp);
            List<FieldVo[]> data = (List<FieldVo[]>) resp.getBody();
            List<PairItem> items = new ArrayList<>(data.size());
            for (FieldVo[] fvs : data) {
                String itemValue = fvs[0].getValue() == null ? "" : fvs[0].getValue().toString();
                String itemLabel = fvs[1].getValue() == null ? "" : fvs[1].getValue().toString();
                items.add(new PairItem(itemValue, itemLabel));
            }
            bg.setItems(items);
        } else
            bg.setItems(new ArrayList<PairItem>());
    }

    @Override
    public Object getValue() {
        return bg.getValue();
    }

    ///////////////////////////////下列针对不同的类型使用不同的类来处理/////////////////////////////////

    private interface Builder {
        void build(Object data);
    }

    private abstract class AbstractBuilder implements Builder, EventListener<Event> {

        private Listbox target;

        @Override
        public void build(Object data) {
            Component source = createComponent(data);
            if (bg.getShowType() == BandboxExt.POPUP) {
                Window dialog = new Window();
                dialog.setTitle(bg.getTitle() == null ? Labels.getLabel("user.select.org.title") : bg.getTitle());
                dialog.setBorder("normal");
                dialog.setClosable(true);
                dialog.setMaximizable(true);
                dialog.setSizable(true);
                dialog.setPosition(bg.getPosition() == null ? "center" : bg.getPosition());
                if (bg.isMultiple()) {
                    dialog.setHeight(bg.getPanelHeight() == null ? "500px" : bg.getPanelHeight());
                    dialog.setWidth(bg.getPanelWidth() == null ? "650px" : bg.getPanelWidth());
                    createBody(dialog, source);
                } else {
                    dialog.appendChild(source);
                    dialog.setHeight(bg.getPanelHeight() == null ? "450px" : bg.getPanelHeight());
                    dialog.setWidth(bg.getPanelWidth() == null ? "550px" : bg.getPanelWidth());
                }
                dialog.setPage(bg.getPage());
                dialog.doHighlighted();
            } else {
                Bandpopup pp = (Bandpopup) bg.getFirstChild();
                if (bg.isMultiple()) {
                    pp.setHeight(bg.getPanelHeight() == null ? "450px" : bg.getPanelHeight());
                    pp.setWidth(bg.getPanelWidth() == null ? "600px" : bg.getPanelWidth());
                    createBody(pp, source);
                } else {
                    pp.appendChild(source);
                    pp.setHeight(bg.getPanelHeight() == null ? "450px" : bg.getPanelHeight());
                    pp.setWidth(bg.getPanelWidth() == null ? "550px" : bg.getPanelWidth());
                }
            }
        }

        private void createBody(Component container, Component source) {
            Borderlayout borderlayout = new Borderlayout();
            borderlayout.setParent(container);
            Center center = new Center();
            center.setHflex("1");
            center.setVflex("1");
            center.setBorder("none");
            center.setParent(borderlayout);

            Hlayout hlayout = new Hlayout();
            hlayout.setHflex("1");
            hlayout.setVflex("1");
            hlayout.appendChild(source);
            hlayout.setParent(center);

            Div vlayout = new Div();
            vlayout.setWidth("26px");
            vlayout.setVflex("1");
            vlayout.setStyle("display:table");
            Vlayout content = new Vlayout();
            content.setSpacing("26px");
            content.setStyle("vertical-align:middle;display:table-cell");
            content.setParent(vlayout);
            A add = new A();
            add.setImage("~./images/rightrightarrow_g.png");
            add.setTooltiptext(Labels.getLabel("button.create"));
            add.addEventListener(Events.ON_CLICK, this);
            add.setParent(content);
            A remove = new A();
            remove.setImage("~./images/leftleftarrow_g.png");
            remove.setTooltiptext(Labels.getLabel("button.remove"));
            remove.addEventListener(Events.ON_CLICK, this);
            remove.setParent(content);
            vlayout.setParent(hlayout);
            createList(hlayout);

            South south = new South();
            south.setSize("36px");
            south.setBorder("none");
            Toolbar toolbar = new Toolbar();
            toolbar.setAlign("end");
            Button close = new Button(Labels.getLabel("button.cancel"));
            close.setIconSclass("z-icon-times");
            close.setParent(toolbar);
            if (bg.getShowType() == BandboxExt.POPUP)
                close.addForward(Events.ON_CLICK, container, Events.ON_CLOSE);
            else
                close.addEventListener(Events.ON_CLICK, this);
            Button confirm = new Button(Labels.getLabel("button.ok"));
            confirm.setIconSclass("z-icon-check");
            confirm.setParent(toolbar);
            confirm.addEventListener(Events.ON_CLICK, this);
            toolbar.setParent(south);
            south.setParent(borderlayout);
        }

        /**
         * 创建已选择的列表
         */
        private void createList(Component parent) {
            target = new Listbox();
            target.setHflex("1");
            target.setVflex("1");
            target.setStyle("margin-right:1px");
            target.setMultiple(true);
            if (bg.getItems() != null && !bg.getItems().isEmpty()) {
                for (PairItem item : bg.getItems()) {
                    Listitem li = new Listitem(item.getLabel());
                    li.setValue(item);
                    li.setParent(target);
                }
            }
            target.setParent(parent);
        }

        @Override
        public void onEvent(Event event) throws Exception {
            Component s = event.getTarget();
            if (s instanceof Button) {
                Button btn = (Button) s;
                //确定按钮
                if (btn.getIconSclass().equals("z-icon-check")) {
                    List<PairItem> items = new ArrayList<>(target.getItemCount());
                    for (Listitem li : target.getItems()) {
                        PairItem item = li.getValue();
                        items.add(item);
                    }
                    bg.setItems(items);
                    Events.postEvent(new Event(Events.ON_CHANGE, bg, null));
                    if (bg.getShowType() == BandboxExt.POPUP)
                        target.getRoot().detach();
                    else
                        bg.getFirstChild().getFirstChild().detach();
                } else
                    bg.close();
            } else if (s instanceof A) {
                if (s.getNextSibling() == null) {//remove
                    List<Listitem> items = new ArrayList<>(target.getSelectedItems());
                    for (Listitem li : items)
                        li.detach();
                } else {//add
                    move(target);
                }
            } else
                dispatch(event);
            event.stopPropagation();
        }

        /**
         * 执行多个栏位映射
         *
         * @param fvs
         */
        protected void doMapping(FieldVo[] fvs) {
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            MapVo mv = null;
            if (anchor != null) {
                ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                        .getKeys();
                mv = new ListMapVo(bg.getMapping(), fvs, support.getComponent()
                        .getId(), data);
            } else if (main instanceof PanelSupport) {
                ListSupport ls = ((PanelSupport) main).getList();
                mv = new ListMapVo(bg.getMapping(), fvs, ls.getComponent()
                        .getId(), null);
            } else
                mv = new MapVo(bg.getMapping(), fvs);
            IResponseMessage<?> resp = cs.doMap(new MapRequestMessage(main
                    .getId(), mv));
            if (resp.isSuccess()) {
                Map<String, Object> data = (Map<String, Object>) resp.getBody();
                for (Map.Entry<String, Object> entry : data.entrySet()) {
                    for (Map.Entry<String, Component> e : main
                            .getManagedComponents().entrySet()) {
                        if (e.getKey().equals(entry.getKey())) {
                            if (e.getValue() != bg) {
                                Object o = PageUtils.getValue(e.getValue(),
                                        false);
                                if (!Lang.equals(o, entry.getValue())) {
                                    PageUtils.setValue(e.getValue(),
                                            entry.getValue());
                                    Event evt = new Event(Events.ON_CHANGE,
                                            e.getValue());
                                    Events.sendEvent(e.getValue(), evt);
                                }
                            }
                            break;
                        }
                    }
                }
            } else
                Clients.wrongValue(bg, (String) resp.getBody());
        }

        protected abstract void move(Listbox target);

        protected abstract void dispatch(Event event);

        protected abstract Component createComponent(Object data);
    }

    private class ListBuilder extends AbstractBuilder {

        private Listbox listbox;

        private Paging paging;

        private Integer[] indexes = new Integer[2];

        @Override
        protected Component createComponent(Object obj) {
            Component comp = null;
            listbox = new Listbox();
            listbox.setHflex("2");
            listbox.setVflex("1");
            listbox.setSpan(bg.isSpan());
            listbox.setMultiple(bg.isMultiple());
            listbox.setSizedByContent(bg.isSizedByContent());
            comp = listbox;
            List<FieldVo[]> data = null;
            if (obj instanceof ListPageVo) {
                if (bg.isFetchAll()) {
                    listbox.setMold("paging");
                    paging = listbox.getPagingChild();
                } else {
                    Vlayout layout = new Vlayout();
                    layout.setSpacing("0");
                    layout.setHflex(listbox.getHflex());
                    layout.setVflex(listbox.getVflex());
                    listbox.setHflex("1");
                    layout.appendChild(listbox);
                    paging = new Paging();
                    paging.setHflex("1");
                    paging.addEventListener(ZulEvents.ON_PAGING, this);
                    layout.appendChild(paging);
                    comp = layout;
                }
                paging.setAutohide(true);
                paging.setDetailed(bg.isPagingDetailed());
                paging.setPageSize(bg.getPageSize());
                if (!Strings.isBlank(bg.getPagingMold()))
                    paging.setMold(bg.getPagingMold());
                if (!Strings.isBlank(bg.getPagingStyle()))
                    paging.setStyle(bg.getPagingStyle());
                ListPageVo lpv = (ListPageVo) obj;
                data = lpv.getData();
                paging.setTotalSize(lpv.getTotalCount());
            } else {
                data = (List<FieldVo[]>) obj;
            }
            createHeader();
            redraw(data);
            return comp;
        }

        /**
         * 重绘树结点
         *
         * @param data
         */
        private void redraw(List<FieldVo[]> data) {
            if (!data.isEmpty()) {
                FieldVo[] first = data.get(0);
                for (int i = 0; i < first.length; i++) {
                    if (first[i].getName().equalsIgnoreCase(bg.getValueField()))
                        indexes[0] = i;
                    else if (first[i].getName().equalsIgnoreCase(bg.getLabelField()))
                        indexes[1] = i;
                }
                if (indexes[0] == null || indexes[1] == null)
                    return;
                ExpressionEngine expressionEngine = null;
                Map<String, Object> evalMap = null;
                Map<String, Component> managedComponents = null;
                if (!Strings.isBlank(bg.getRowScript())) {
                    String script = bg.getRowScript();
                    if (bg.getRowScript().startsWith("$")) {
                        script = (String) WebUtils.getFieldValue(main, bg.getRowScript().substring(1));
                        bg.setRowScript(script);
                    }
                    if (script != null) {
                        expressionEngine = SupportFactory.getExpressionEngine(main, script);
                        expressionEngine.compile();
                        evalMap = new HashMap<>();
                        managedComponents = new HashMap<>();
                    }
                }
                try {
                    int cols = listbox.getListhead() == null ? 1 : listbox.getListhead().getChildren().size();
                    for (FieldVo[] fvs : data) {
                        if (expressionEngine != null) {
                            evalMap.clear();
                            managedComponents.clear();
                            for (FieldVo fv : fvs)
                                evalMap.put(fv.getName(), fv.getValue());
                        }
                        Listitem li = new Listitem();
                        for (int i = 0; i < cols; i++) {
                            Object value = fvs[i].getValue();
                            Listcell cell = new Listcell(value == null ? "" : value.toString());
                            cell.setParent(li);
                            if (expressionEngine != null)
                                managedComponents.put(fvs[i].getName(), cell);
                        }
                        li.setValue(fvs);
                        if (!bg.isMultiple())
                            li.addEventListener(Events.ON_DOUBLE_CLICK, this);
                        listbox.appendChild(li);
                        if (expressionEngine != null)
                            expressionEngine.eval(li, evalMap, managedComponents);
                    }
                } finally {
                    if (expressionEngine != null) {
                        expressionEngine.destroy();
                        expressionEngine = null;
                        evalMap = null;
                        managedComponents = null;
                    }
                }
            }
        }

        /**
         * 创建表头
         */
        private void createHeader() {
            if (!Strings.isBlank(bg.getHeaders())) {
                String title = bg.getHeaders();
                if (title.startsWith("$")) {
                    Object val = WebUtils.getFieldValue(main, title.substring(1));
                    if (val == null || val.toString().equals(""))
                        return;
                    title = (String) val;
                }
                Listhead head = new Listhead();
                head.setSizable(bg.isSizable());
                String[] headers = title.split(",");
                for (String h : headers) {
                    Listheader header = new Listheader(h.trim());
                    header.setHflex("1");
                    head.appendChild(header);
                }
                listbox.appendChild(head);
                if (listbox.getFrozen() == null && bg.getFrozenColumns() > 0) {
                    Frozen frozen = new Frozen();
                    if (!Strings.isBlank(bg.getFrozenStyle()))
                        frozen.setStyle(bg.getFrozenStyle());
                    frozen.setColumns(bg.getFrozenColumns());
                    listbox.appendChild(frozen);
                }
            }
        }

        /**
         * 分页
         *
         * @param pageNo
         */
        private void paging(int pageNo) {
            GetListVo gv = null;
            if (anchor != null) {
                ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                        .getKeys();
                gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), support
                        .getComponent().getId(), data);
            } else if (main instanceof PanelSupport) {
                ListSupport ls = ((PanelSupport) main).getList();
                gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), ls
                        .getComponent().getId(), null);
            } else
                gv = new GetListVo(bg.getDbId(), bg.getQuery());
            gv.setFilter(bg.getFilter());
            gv.setPageSize(bg.getPageSize());
            gv.setOrderBy(bg.getOrderBy());
            gv.setPageNo(pageNo + 1);
            GetListRequestMessage req = new GetListRequestMessage(main.getId(), gv);
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            IResponseMessage<?> resp = cs.getList(req);
            if (!resp.isSuccess()) {
                Clients.wrongValue(paging, (String) resp.getBody());
                return;
            }
            listbox.getItems().clear();
            redraw((List<FieldVo[]>) resp.getBody());
        }

        @Override
        protected void move(Listbox target) {
            for (Listitem ti : listbox.getSelectedItems()) {
                FieldVo[] node = ti.getValue();
                Object id = node[indexes[0]].getValue();
                if (!bg.exists(id)) {
                    Object label = node[indexes[1]].getValue();
                    Listitem li = new Listitem(label == null ? "" : label.toString());
                    PairItem item = new PairItem(id, li.getLabel());
                    li.setValue(item);
                    target.appendChild(li);
                    bg.getItems().add(item);
                }
            }
        }

        @Override
        protected void dispatch(Event event) {
            if (event.getName().equals(Events.ON_DOUBLE_CLICK)) {
                Listitem ti = (Listitem) event.getTarget();
                FieldVo[] node = ti.getValue();
                Object id = node[indexes[0]].getValue();
                Object name = node[indexes[1]].getValue();
                bg.setItem(id, name == null ? "" : name.toString());
                Events.postEvent(new Event(Events.ON_CHANGE, bg, null));
                if (bg.getShowType() == BandboxExt.POPUP)
                    listbox.getRoot().detach();
                else
                    bg.getFirstChild().getFirstChild().detach();
                if (!Strings.isBlank(bg.getMapping()))
                    doMapping(node);
            } else if (event instanceof PagingEvent) {
                PagingEvent pe = (PagingEvent) event;
                paging(pe.getActivePage());
            }
        }

    }

    private class GroupBuilder extends AbstractBuilder {

        private Tree tree;

        private Paging paging;

        private Integer[] indexes = new Integer[2];

        @Override
        protected Component createComponent(Object obj) {
            Component comp = null;
            tree = new Tree();
            tree.setHflex("2");
            tree.setVflex("1");
            tree.setSpan(bg.isSpan());
            tree.setMultiple(bg.isMultiple());
            tree.setSizedByContent(bg.isSizedByContent());
            comp = tree;
            List<FieldVo[]> data = null;
            if (obj instanceof ListPageVo) {
                if (bg.isFetchAll()) {
                    tree.setMold("paging");
                    paging = tree.getPagingChild();
                } else {
                    Vlayout layout = new Vlayout();
                    layout.setSpacing("0");
                    layout.setHflex(tree.getHflex());
                    layout.setVflex(tree.getVflex());
                    tree.setHflex("1");
                    layout.appendChild(tree);
                    paging = new Paging();
                    paging.setHflex("1");
                    paging.addEventListener(ZulEvents.ON_PAGING, this);
                    layout.appendChild(paging);
                    comp = layout;
                }
                paging.setAutohide(true);
                paging.setDetailed(bg.isPagingDetailed());
                paging.setPageSize(bg.getPageSize());
                if (!Strings.isBlank(bg.getPagingMold()))
                    paging.setMold(bg.getPagingMold());
                if (!Strings.isBlank(bg.getPagingStyle()))
                    paging.setStyle(bg.getPagingStyle());
                ListPageVo lpv = (ListPageVo) obj;
                data = lpv.getData();
                paging.setTotalSize(lpv.getTotalCount());
            } else {
                data = (List<FieldVo[]>) obj;
            }
            createHeader();
            redraw(data);
            return comp;
        }

        /**
         * 重绘树结点
         *
         * @param data
         */
        private void redraw(List<FieldVo[]> data) {
            if (!data.isEmpty()) {
                ExpressionEngine expressionEngine = null;
                if (!Strings.isBlank(bg.getRowScript())) {
                    String script = bg.getRowScript();
                    if (bg.getRowScript().startsWith("$")) {
                        script = (String) WebUtils.getFieldValue(main, bg.getRowScript().substring(1));
                        bg.setRowScript(script);
                    }
                    if (script != null) {
                        expressionEngine = SupportFactory.getExpressionEngine(main, script);
                        expressionEngine.compile();
                    }
                }
                try {
                    Node root = null;
                    if (type == DEFAULT)
                        root = createDefaultModel(data);
                    else
                        root = createGroupModel(data);
                    Treechildren tc = new Treechildren();
                    for (Node node : root.getChildren()) {
                        Treeitem ti = new Treeitem();
                        if (node.getData() == null) {//type=DEFAULT
                            ti.setLabel((String) node.getId());
                            ti.setSelectable(false);
                        } else {
                            createRow(expressionEngine, ti, node.getData());
                            if (type != DUAL) {
                                if (!bg.isMultiple())
                                    ti.addEventListener(Events.ON_DOUBLE_CLICK, this);
                            } else
                                ti.setSelectable(false);
                        }
                        ti.setValue(node);
                        if (!node.isLeaf()) {
                            ti.appendChild(new Treechildren());
                            if (bg.isLazy()) {
                                if (bg.getDepth() > 0)
                                    createNode(expressionEngine, ti, node.getChildren());
                                else {
                                    ti.addEventListener(Events.ON_OPEN, this);
                                    ti.setOpen(false);
                                }
                            } else {
                                createNode(expressionEngine, ti, node.getChildren());
                            }//else
                        } else if (type == GROUP || bg.getDepth() > 0) {
                            ti.appendChild(new Treechildren());
                            ti.addEventListener(Events.ON_OPEN, this);
                            ti.setOpen(false);
                        }
                        tc.appendChild(ti);
                    }
                    tree.appendChild(tc);
                } finally {
                    if (expressionEngine != null) {
                        expressionEngine.destroy();
                        expressionEngine = null;
                    }
                }
            }
        }

        @Override
        protected void move(Listbox target) {
            for (Treeitem ti : tree.getSelectedItems()) {
                Node node = ti.getValue();
                if ((type == DUAL && node.getId() == null) || (type != DUAL && node.getData() != null)) {
                    Object id = node.getData()[indexes[0]].getValue();
                    if (!bg.exists(id)) {
                        Object label = node.getData()[indexes[1]].getValue();
                        Listitem li = new Listitem(label == null ? "" : label.toString());
                        PairItem item = new PairItem(id, li.getLabel());
                        li.setValue(item);
                        target.appendChild(li);
                        bg.getItems().add(item);
                    }
                }
            }
        }

        @Override
        protected void dispatch(Event event) {
            if (event instanceof OpenEvent) {
                OpenEvent oe = (OpenEvent) event;
                if (oe.isOpen()) {
                    ExpressionEngine expressionEngine = null;
                    if (!Strings.isBlank(bg.getRowScript())) {
                        expressionEngine = SupportFactory.getExpressionEngine(main, bg.getRowScript());
                        expressionEngine.compile();
                    }
                    try {
                        Treeitem ti = (Treeitem) oe.getTarget();
                        if (type == GROUP) {
                            loadGroup(expressionEngine, ti);
                        } else {
                            Node parent = ti.getValue();
                            if (parent.getChildren() == null)
                                loadGroup(expressionEngine, ti);
                            else
                                createNode(expressionEngine, ti, parent.getChildren());
                        }
                        ti.removeEventListener(Events.ON_OPEN, this);
                    } finally {
                        if (expressionEngine != null) {
                            expressionEngine.destroy();
                            expressionEngine = null;
                        }
                    }
                }
            } else if (event.getName().equals(Events.ON_DOUBLE_CLICK)) {
                Treeitem ti = (Treeitem) event.getTarget();
                Node node = ti.getValue();
                Object id = node.getData()[indexes[0]].getValue();
                Object name = node.getData()[indexes[1]].getValue();
                bg.setItem(id, name == null ? "" : name.toString());
                Events.postEvent(new Event(Events.ON_CHANGE, bg, null));
                if (bg.getShowType() == BandboxExt.POPUP)
                    tree.getRoot().detach();
                else
                    bg.getFirstChild().getFirstChild().detach();
                if (!Strings.isBlank(bg.getMapping()))
                    doMapping(node.getData());
            } else if (event instanceof PagingEvent) {
                PagingEvent pe = (PagingEvent) event;
                paging(pe.getActivePage());
            }
        }

        /**
         * 创建子结点
         *
         * @param parent
         * @param children
         */
        private void createNode(ExpressionEngine expressionEngine, Treeitem parent, List<Node> children) {
            for (Node node : children) {
                Treeitem ti = new Treeitem();
                if (node.getData() == null) {
                    ti.setLabel((String) node.getId());
                    ti.setSelectable(false);
                } else {
                    createRow(expressionEngine, ti, node.getData());
                    if (type != DUAL) {
                        if (!bg.isMultiple())
                            ti.addEventListener(Events.ON_DOUBLE_CLICK, this);
                    } else
                        ti.setSelectable(false);
                }
                ti.setValue(node);
                if (!node.isLeaf()) {
                    ti.appendChild(new Treechildren());
                    if (bg.isLazy()) {
                        ti.addEventListener(Events.ON_OPEN, this);
                        ti.setOpen(false);
                    } else {
                        createNode(expressionEngine, ti, node.getChildren());
                        if (bg.getDepth() > 0)
                            ti.setOpen(bg.getDepth() >= node.getLevel());
                    }
                } else if (type == DUAL) {
                    ti.addEventListener(Events.ON_OPEN, this);
                    ti.appendChild(new Treechildren());
                    ti.setOpen(false);
                }
                parent.getTreechildren().appendChild(ti);
            }
            if (type == DUAL)
                loadGroup(expressionEngine, parent);
        }

        /**
         * 分页
         *
         * @param pageNo
         */
        private void paging(int pageNo) {
            GetListVo gv = null;
            if (anchor != null) {
                ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                        .getKeys();
                gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), support
                        .getComponent().getId(), data);
            } else if (main instanceof PanelSupport) {
                ListSupport ls = ((PanelSupport) main).getList();
                gv = new ListGetListVo(bg.getDbId(), bg.getQuery(), ls
                        .getComponent().getId(), null);
            } else
                gv = new GetListVo(bg.getDbId(), bg.getQuery());
            gv.setFilter(bg.getFilter());
            gv.setPageSize(bg.getPageSize());
            gv.setOrderBy(bg.getOrderBy());
            gv.setPageNo(pageNo + 1);
            GetListRequestMessage req = new GetListRequestMessage(main.getId(), gv);
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            IResponseMessage<?> resp = cs.getList(req);
            if (!resp.isSuccess()) {
                Clients.wrongValue(paging, (String) resp.getBody());
                return;
            }
            if (tree.getTreechildren() != null)
                tree.getTreechildren().detach();
            redraw((List<FieldVo[]>) resp.getBody());
        }

        /**
         * 创建行
         *
         * @param expressionEngine
         * @param ti
         * @param fvs
         */
        private void createRow(ExpressionEngine expressionEngine, Treeitem ti, FieldVo[] fvs) {
            createRow(expressionEngine, ti, fvs, false);
        }

        /**
         * 创建行
         *
         * @param expressionEngine
         * @param ti
         * @param fvs
         */
        private void createRow(ExpressionEngine expressionEngine, Treeitem ti, FieldVo[] fvs, boolean dual) {
            int cols = 1;
            if (type != DUAL || dual) {
                cols = tree.getTreecols() == null ? 1 : tree.getTreecols().getChildren().size();
                if (fvs.length < cols)
                    cols = fvs.length;
            }
            Map<String, Object> data = null;
            Map<String, Component> managedComponents = null;
            if (expressionEngine != null) {
                data = new HashMap<>();
                for (FieldVo fv : fvs)
                    data.put(fv.getName(), fv.getValue());
                managedComponents = new HashMap<>();
            }
            Treerow tr = new Treerow();
            tr.setParent(ti);
            for (int i = 0; i < cols; i++) {
                Object label = fvs[i].getValue();
                Treecell cell = new Treecell(label == null ? "" : label.toString());
                cell.setParent(tr);
                if (managedComponents != null)
                    managedComponents.put(fvs[i].getName(), cell);
            }
            if (expressionEngine != null) {
                expressionEngine.eval(ti, data, managedComponents);
                data = null;
                managedComponents = null;
            }
        }

        /**
         * 加载第2组数据
         *
         * @param ti
         */
        protected void loadGroup(ExpressionEngine expressionEngine, Treeitem ti) {
            Node node = ti.getValue();
            if (Strings.isBlank(bg.getGroupQuery())) {
                int pos = bg.getQuery().toLowerCase().indexOf("where");
                if (pos < 0) {
                    ti.getTreechildren().detach();
                    return;
                } else {
                    StringBuilder sb = new StringBuilder();
                    sb.append(bg.getQuery().substring(0, pos)).append(" WHERE ").append(bg.getGroupField().split(",")[1]).append("=?");
                    if (!Strings.isBlank(bg.getOrderBy()))
                        sb.append(" ORDER BY ").append(bg.getOrderBy());
                    bg.setGroupQuery(sb.toString());
                    bg.setAttribute("gg", "");
                }
            } else if (!bg.isFetchAll() && type == DUAL) {
                int pos = bg.getQuery().toLowerCase().indexOf("where");
                if (pos > 0) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(bg.getQuery().substring(0, pos)).append(" WHERE ").append(bg.getGroupField().split(",")[1]).append("=?");
                    if (!Strings.isBlank(bg.getOrderBy()))
                        sb.append(" ORDER BY ").append(bg.getOrderBy());
                    GetListVo gv = new GetListVo(bg.getDbId(), sb.toString());
                    gv.setParameter(node.getId());
                    ComponentService cs = ServiceLocator.lookup(ComponentService.class);
                    IResponseMessage<?> resp = cs.getList(new GetListRequestMessage(main.getId(), gv));
                    if (!resp.isSuccess())
                        MessageBox.showMessage(resp);
                    else {
                        List<FieldVo[]> data = (List<FieldVo[]>) resp.getBody();
                        if (!data.isEmpty()) {
                            Integer[] gidx = new Integer[2];
                            String[] groupFields = bg.getGroupField().split(",");
                            FieldVo[] first = data.get(0);
                            for (int i = 0; i < first.length; i++) {
                                if (first[i].getName().equalsIgnoreCase(groupFields[0]))
                                    gidx[0] = i;
                                if (first[i].getName().equalsIgnoreCase(groupFields[1]))
                                    gidx[1] = i;
                            }
                            for (FieldVo[] fvs : data) {
                                Treeitem child = new Treeitem();
                                child.setValue(new Node(fvs[gidx[0]].getValue(), fvs[gidx[1]].getValue(), fvs));
                                createRow(expressionEngine, child, fvs);
                                child.appendChild(new Treechildren());
                                child.addEventListener(Events.ON_OPEN, this);
                                child.setOpen(false);
                                child.setSelectable(false);
                                ti.getTreechildren().appendChild(child);
                            }
                        }
                    }
                }
            }
            GetListVo gv = new GetListVo(bg.getDbId(), bg.getGroupQuery());
            gv.setParameter(node.getId());
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            IResponseMessage<?> resp = cs.getList(new GetListRequestMessage(main.getId(), gv));
            if (!resp.isSuccess())
                MessageBox.showMessage(resp);
            else {
                List<FieldVo[]> data = (List<FieldVo[]>) resp.getBody();
                if (!data.isEmpty()) {
                    int gidx = 0;
                    if (type == GROUP) {
                        FieldVo[] first = data.get(0);
                        for (; gidx < first.length; gidx++) {
                            if (first[gidx].getName().equalsIgnoreCase(bg.getGroupField()))
                                break;
                        }
                    } else if (bg.getAttribute("gg") != null) {
                        FieldVo[] first = data.get(0);
                        for (; gidx < first.length; gidx++) {
                            if (first[gidx].getName().equalsIgnoreCase(bg.getValueField()))
                                break;
                        }
                    }
                    Component parent = ti.getTreechildren();
                    if (type == DUAL && !Strings.isBlank(bg.getDualName())) {
                        if (parent.getFirstChild() != null) {//有第一分组存在
                            Treeitem child = new Treeitem(bg.getDualName());
                            child.appendChild(new Treechildren());
                            child.setParent(parent);
                            parent = child.getTreechildren();
                        }
                    }
                    for (FieldVo[] fvs : data) {
                        Treeitem child = new Treeitem();
                        if (type == DUAL) {
                            createRow(expressionEngine, child, fvs, true);
                            child.setImage("~./images/weather_sun.png");
                            child.setValue(new Node(fvs));
                            if (indexes[0] == null) {
                                for (int i = 0; i < fvs.length; i++) {
                                    if (fvs[i].getName().equalsIgnoreCase(bg.getGroupField()))
                                        gidx = i;
                                    if (fvs[i].getName().equalsIgnoreCase(bg.getValueField()))
                                        indexes[0] = i;
                                    else if (fvs[i].getName().equalsIgnoreCase(bg.getLabelField()))
                                        indexes[1] = i;
                                }
                            }
                        } else {
                            createRow(expressionEngine, child, fvs);
                            child.setValue(new Node(fvs[gidx].getValue(), fvs));
                            child.appendChild(new Treechildren());
                            child.addEventListener(Events.ON_OPEN, this);
                            child.setOpen(false);
                        }
                        child.setParent(parent);
                        if (!bg.isMultiple())
                            child.addEventListener(Events.ON_DOUBLE_CLICK, this);
                    }//for
                } else if (type == GROUP || bg.getAttribute("gg") != null) //if
                    ti.getTreechildren().detach();
            }//else
        }

        /**
         * 根据数据生成分组模型结构
         *
         * @param data
         * @return
         */
        private Node createGroupModel(List<FieldVo[]> data) {
            if (type == GROUP) {
                Integer gidx = null;
                FieldVo[] first = data.get(0);
                for (int i = 0; i < first.length; i++) {
                    if (first[i].getName().equalsIgnoreCase(bg.getGroupField()))
                        gidx = i;
                    if (first[i].getName().equalsIgnoreCase(bg.getValueField()))
                        indexes[0] = i;
                    else if (first[i].getName().equalsIgnoreCase(bg.getLabelField()))
                        indexes[1] = i;
                }
                if (gidx != null && indexes[0] != null && indexes[1] != null) {
                    Node root = new Node(data.size());
                    for (FieldVo[] fvs : data)
                        root.appendChild(new Node(fvs[gidx].getValue(), fvs));
                    return root;
                }
                return new Node(0);
            } else {
                Integer[] gidx = new Integer[2];
                String[] groupFields = bg.getGroupField().split(",");
                FieldVo[] first = data.get(0);
                for (int i = 0; i < first.length; i++) {
                    if (first[i].getName().equalsIgnoreCase(groupFields[0]))
                        gidx[0] = i;
                    if (first[i].getName().equalsIgnoreCase(groupFields[1]))
                        gidx[1] = i;
                    if (type != DUAL) {
                        if (first[i].getName().equalsIgnoreCase(bg.getValueField()))
                            indexes[0] = i;
                        else if (first[i].getName().equalsIgnoreCase(bg.getLabelField()))
                            indexes[1] = i;
                    }
                }
                if (gidx[0] == null || gidx[1] == null)
                    return new Node(0);
                Node root = new Node(1);
                if (bg.isFetchAll()) {
                    Map<Object, Node> map = new LinkedHashMap<>();
                    List<Node> pending = new ArrayList<>();
                    for (FieldVo[] fvs : data) {
                        Node node = new Node(fvs[gidx[0]].getValue(), fvs[gidx[1]].getValue(), fvs);
                        Node parent = map.get(node.getParentId());
                        if (parent != null)
                            parent.appendChild(node);
                        else
                            pending.add(node);
                        map.put(node.getId(), node);
                    }
                    Iterator<Node> itr = pending.iterator();
                    while (itr.hasNext()) {
                        Node node = itr.next();
                        Node parent = map.get(node.getParentId());
                        if (parent != null) {
                            parent.appendChild(node);
                            itr.remove();
                        } else
                            root.appendChild(node);
                    }
                } else {
                    for (FieldVo[] fvs : data)
                        root.appendChild(new Node(fvs[gidx[0]].getValue(), fvs[gidx[1]].getValue(), fvs));
                }
                return root;
            }
        }

        /**
         * 根据数据生成树模型
         *
         * @param data
         * @return
         */
        private Node createDefaultModel(List<FieldVo[]> data) {
            String[] groups = bg.getGroupField().split(";");
            Integer[][] gidx = new Integer[groups.length][];
            FieldVo[] first = data.get(0);
            for (int i = 0; i < groups.length; i++) {
                String group = groups[i];
                String[] fields = group.split(",");
                gidx[i] = new Integer[fields.length];
                for (int j = 0; j < fields.length; j++) {
                    for (int k = 0; k < first.length; k++) {
                        if (fields[j].equalsIgnoreCase(first[k].getName())) {
                            gidx[i][j] = k;
                            break;
                        }
                    }
                }
            }
            for (int i = 0; i < first.length; i++) {
                if (first[i].getName().equalsIgnoreCase(bg.getValueField()))
                    indexes[0] = i;
                else if (first[i].getName().equalsIgnoreCase(bg.getLabelField()))
                    indexes[1] = i;
            }
            //检查是否完整
            for (int i = 0; i < gidx.length; i++) {
                for (int j = 0; j < gidx[i].length; j++) {
                    if (gidx[i][j] == null)
                        return new Node(0);
                }
            }
            if (indexes[0] == null || indexes[1] == null)
                return new Node(0);
            Node root = new Node(1);
            Map<Object, Node> map = new HashMap<>();
            StringBuilder sb = new StringBuilder();
            StringBuilder tmp = new StringBuilder();
            for (FieldVo[] fvs : data) {
                for (int i = 0; i < gidx[0].length; i++)
                    sb.append(fvs[gidx[0][i]].getValue());
                String parentId = sb.toString();
                Node parent = map.get(parentId);
                if (parent == null) {
                    parent = new Node(parentId);
                    map.put(parent.getId(), parent);
                    root.appendChild(parent);
                }
                for (int i = 1; i < gidx.length; i++) {
                    tmp.setLength(0);
                    for (int j = 0; j < gidx[i].length; j++) {
                        sb.append(fvs[gidx[i][j]].getValue());
                        tmp.append(fvs[gidx[i][j]].getValue());
                    }
                    Node node = map.get(sb.toString());
                    if (node == null) {
                        node = new Node(tmp.toString());
                        map.put(sb.toString(), node);
                        parent.appendChild(node);
                    }
                    parent = node;
                }
                parent.appendChild(new Node(fvs));
                sb.setLength(0);
            }
            return root;
        }

        /**
         * 创建表头
         */
        private void createHeader() {
            if (!Strings.isBlank(bg.getHeaders())) {
                String title = bg.getHeaders();
                if (title.startsWith("$")) {
                    Object val = WebUtils.getFieldValue(main, title.substring(1));
                    if (val == null || val.toString().equals(""))
                        return;
                    title = (String) val;
                }
                Treecols head = new Treecols();
                head.setSizable(bg.isSizable());
                String[] headers = title.split(",");
                for (String h : headers) {
                    Treecol header = new Treecol(h.trim());
                    header.setHflex("1");
                    head.appendChild(header);
                }
                tree.appendChild(head);
                if (tree.getFrozen() == null && bg.getFrozenColumns() > 0) {
                    Frozen frozen = new Frozen();
                    if (!Strings.isBlank(bg.getFrozenStyle()))
                        frozen.setStyle(bg.getFrozenStyle());
                    frozen.setColumns(bg.getFrozenColumns());
                    tree.appendChild(frozen);
                }
            }
        }
    }

}
